# -*- coding: utf-8 -*-
"""
Allure工具类

提供Allure测试报告的增强功能和工具方法
"""

import os
import json
import time
import allure
import base64
from typing import Dict, Any, Optional, List, Union
from pathlib import Path
from datetime import datetime
from requests import Response
from utils.logger import get_logger

# 配置日志
logger = get_logger(__name__)


class AllureHelper:
    """
    Allure测试报告助手类
    
    提供丰富的Allure功能支持，包括附件、步骤、标签等
    """
    
    @staticmethod
    def attach_text(content: str, name: str = "文本附件", attachment_type: allure.attachment_type = allure.attachment_type.TEXT):
        """
        添加文本附件
        
        Args:
            content: 文本内容
            name: 附件名称
            attachment_type: 附件类型
        """
        try:
            allure.attach(content, name=name, attachment_type=attachment_type)
            logger.debug(f"已添加文本附件: {name}")
        except Exception as e:
            logger.error(f"添加文本附件失败: {e}")
    
    @staticmethod
    def attach_json(data: Union[Dict, List], name: str = "JSON数据"):
        """
        添加JSON附件
        
        Args:
            data: JSON数据
            name: 附件名称
        """
        try:
            json_str = json.dumps(data, ensure_ascii=False, indent=2)
            allure.attach(json_str, name=name, attachment_type=allure.attachment_type.JSON)
            logger.debug(f"已添加JSON附件: {name}")
        except Exception as e:
            logger.error(f"添加JSON附件失败: {e}")
    
    @staticmethod
    def attach_response(response: Response, name: str = "HTTP响应"):
        """
        添加HTTP响应附件
        
        Args:
            response: HTTP响应对象
            name: 附件名称
        """
        try:
            # 构建响应信息
            response_info = {
                "status_code": response.status_code,
                "headers": dict(response.headers),
                "url": str(response.url),
                "elapsed": str(response.elapsed),
                "encoding": response.encoding
            }
            
            # 尝试解析响应体
            try:
                if response.headers.get('content-type', '').startswith('application/json'):
                    response_info["body"] = response.json()
                else:
                    response_info["body"] = response.text
            except Exception:
                response_info["body"] = "<无法解析响应体>"
            
            # 添加附件
            AllureHelper.attach_json(response_info, name)
            
        except Exception as e:
            logger.error(f"添加响应附件失败: {e}")
    
    @staticmethod
    def attach_request(method: str, url: str, headers: Optional[Dict] = None, 
                      params: Optional[Dict] = None, data: Optional[Any] = None, 
                      name: str = "HTTP请求"):
        """
        添加HTTP请求附件
        
        Args:
            method: HTTP方法
            url: 请求URL
            headers: 请求头
            params: 查询参数
            data: 请求体数据
            name: 附件名称
        """
        try:
            request_info = {
                "method": method.upper(),
                "url": url,
                "headers": headers or {},
                "params": params or {},
                "data": data
            }
            
            AllureHelper.attach_json(request_info, name)
            
        except Exception as e:
            logger.error(f"添加请求附件失败: {e}")
    
    @staticmethod
    def attach_file(file_path: Union[str, Path], name: Optional[str] = None):
        """
        添加文件附件
        
        Args:
            file_path: 文件路径
            name: 附件名称
        """
        try:
            file_path = Path(file_path)
            if not file_path.exists():
                logger.warning(f"文件不存在: {file_path}")
                return
            
            attachment_name = name or file_path.name
            
            # 根据文件扩展名确定附件类型
            suffix = file_path.suffix.lower()
            if suffix in ['.json']:
                attachment_type = allure.attachment_type.JSON
            elif suffix in ['.xml']:
                attachment_type = allure.attachment_type.XML
            elif suffix in ['.html', '.htm']:
                attachment_type = allure.attachment_type.HTML
            elif suffix in ['.png']:
                attachment_type = allure.attachment_type.PNG
            elif suffix in ['.jpg', '.jpeg']:
                attachment_type = allure.attachment_type.JPG
            elif suffix in ['.csv']:
                attachment_type = allure.attachment_type.CSV
            else:
                attachment_type = allure.attachment_type.TEXT
            
            # 读取文件内容
            if attachment_type in [allure.attachment_type.PNG, allure.attachment_type.JPG]:
                with open(file_path, 'rb') as f:
                    content = f.read()
            else:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
            
            allure.attach(content, name=attachment_name, attachment_type=attachment_type)
            logger.debug(f"已添加文件附件: {attachment_name}")
            
        except Exception as e:
            logger.error(f"添加文件附件失败: {e}")
    
    @staticmethod
    def step(title: str):
        """
        创建测试步骤装饰器
        
        Args:
            title: 步骤标题
        
        Returns:
            allure.step装饰器
        """
        return allure.step(title)
    
    @staticmethod
    def add_environment_info(env_info: Dict[str, str]):
        """
        添加环境信息
        
        Args:
            env_info: 环境信息字典
        """
        try:
            # 创建environment.properties文件
            env_file = Path("allure-results") / "environment.properties"
            env_file.parent.mkdir(exist_ok=True)
            
            with open(env_file, 'w', encoding='utf-8') as f:
                for key, value in env_info.items():
                    f.write(f"{key}={value}\n")
            
            logger.debug("已添加环境信息")
            
        except Exception as e:
            logger.error(f"添加环境信息失败: {e}")
    
    @staticmethod
    def add_executor_info(executor_info: Dict[str, Any]):
        """
        添加执行器信息
        
        Args:
            executor_info: 执行器信息
        """
        try:
            # 创建executor.json文件
            executor_file = Path("allure-results") / "executor.json"
            executor_file.parent.mkdir(exist_ok=True)
            
            with open(executor_file, 'w', encoding='utf-8') as f:
                json.dump(executor_info, f, ensure_ascii=False, indent=2)
            
            logger.debug("已添加执行器信息")
            
        except Exception as e:
            logger.error(f"添加执行器信息失败: {e}")
    
    @staticmethod
    def set_test_info(epic: str = None, feature: str = None, story: str = None, 
                     title: str = None, description: str = None, 
                     severity: allure.severity_level = None, 
                     tags: List[str] = None, links: List[Dict[str, str]] = None):
        """
        设置测试用例信息
        
        Args:
            epic: 史诗
            feature: 功能
            story: 用户故事
            title: 标题
            description: 描述
            severity: 严重程度
            tags: 标签列表
            links: 链接列表
        """
        def decorator(func):
            if epic:
                func = allure.epic(epic)(func)
            if feature:
                func = allure.feature(feature)(func)
            if story:
                func = allure.story(story)(func)
            if title:
                func = allure.title(title)(func)
            if description:
                func = allure.description(description)(func)
            if severity:
                func = allure.severity(severity)(func)
            if tags:
                for tag in tags:
                    func = allure.tag(tag)(func)
            if links:
                for link in links:
                    func = allure.link(link.get('url', ''), name=link.get('name', ''), link_type=link.get('type', 'link'))(func)
            
            return func
        return decorator
    
    @staticmethod
    def add_issue_link(issue_id: str, url: str = None):
        """
        添加问题链接
        
        Args:
            issue_id: 问题ID
            url: 问题URL
        """
        if url:
            return allure.link(url, name=f"Issue-{issue_id}", link_type="issue")
        else:
            return allure.issue(issue_id)
    
    @staticmethod
    def add_test_case_link(case_id: str, url: str = None):
        """
        添加测试用例链接
        
        Args:
            case_id: 用例ID
            url: 用例URL
        """
        if url:
            return allure.link(url, name=f"TestCase-{case_id}", link_type="test_case")
        else:
            return allure.testcase(case_id)
    
    @staticmethod
    def create_test_suite_info():
        """
        创建测试套件信息
        """
        try:
            suite_info = {
                "name": "API自动化测试套件",
                "description": "基于FastAPI和pytest的API接口自动化测试",
                "start": int(time.time() * 1000),
                "uuid": f"suite-{int(time.time())}"
            }
            
            # 创建suites目录
            suites_dir = Path("allure-results") / "suites"
            suites_dir.mkdir(parents=True, exist_ok=True)
            
            # 写入套件信息
            suite_file = suites_dir / f"{suite_info['uuid']}.json"
            with open(suite_file, 'w', encoding='utf-8') as f:
                json.dump(suite_info, f, ensure_ascii=False, indent=2)
            
            logger.debug("已创建测试套件信息")
            
        except Exception as e:
            logger.error(f"创建测试套件信息失败: {e}")
    
    @staticmethod
    def cleanup_old_results(days: int = 7):
        """
        清理旧的测试结果
        
        Args:
            days: 保留天数
        """
        try:
            results_dir = Path("allure-results")
            if not results_dir.exists():
                return
            
            cutoff_time = time.time() - (days * 24 * 60 * 60)
            
            for file_path in results_dir.rglob("*"):
                if file_path.is_file() and file_path.stat().st_mtime < cutoff_time:
                    file_path.unlink()
                    logger.debug(f"已删除旧文件: {file_path}")
            
            logger.info(f"已清理{days}天前的测试结果")
            
        except Exception as e:
            logger.error(f"清理旧结果失败: {e}")


class AllureReporter:
    """
    Allure报告生成器
    
    提供报告生成和管理功能
    """
    
    def __init__(self, results_dir: str = "allure-results", report_dir: str = "allure-report"):
        self.results_dir = Path(results_dir)
        self.report_dir = Path(report_dir)
        self.logger = get_logger(self.__class__.__name__)
    
    def generate_report(self, clean: bool = True, single_file: bool = False) -> bool:
        """
        生成Allure报告
        
        Args:
            clean: 是否清理旧报告
            single_file: 是否生成单文件报告
        
        Returns:
            bool: 生成是否成功
        """
        try:
            import subprocess
            
            # 构建命令
            cmd = ["allure", "generate", str(self.results_dir), "-o", str(self.report_dir)]
            
            if clean:
                cmd.append("--clean")
            
            if single_file:
                cmd.append("--single-file")
            
            # 执行命令
            result = subprocess.run(cmd, capture_output=True, text=True)
            
            if result.returncode == 0:
                self.logger.info(f"Allure报告生成成功: {self.report_dir}")
                return True
            else:
                self.logger.error(f"Allure报告生成失败: {result.stderr}")
                return False
                
        except FileNotFoundError:
            self.logger.error("Allure命令行工具未安装，请先安装Allure")
            return False
        except Exception as e:
            self.logger.error(f"生成报告时发生异常: {e}")
            return False
    
    def serve_report(self, port: int = 8080) -> bool:
        """
        启动Allure报告服务
        
        Args:
            port: 服务端口
        
        Returns:
            bool: 启动是否成功
        """
        try:
            import subprocess
            
            cmd = ["allure", "serve", str(self.results_dir), "-p", str(port)]
            
            self.logger.info(f"启动Allure报告服务，端口: {port}")
            subprocess.run(cmd)
            
            return True
            
        except FileNotFoundError:
            self.logger.error("Allure命令行工具未安装，请先安装Allure")
            return False
        except Exception as e:
            self.logger.error(f"启动报告服务时发生异常: {e}")
            return False
    
    def get_report_summary(self) -> Optional[Dict[str, Any]]:
        """
        获取报告摘要信息
        
        Returns:
            Dict: 报告摘要
        """
        try:
            # 查找widgets目录下的summary.json文件
            summary_file = self.report_dir / "widgets" / "summary.json"
            
            if summary_file.exists():
                with open(summary_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            else:
                self.logger.warning("未找到报告摘要文件")
                return None
                
        except Exception as e:
            self.logger.error(f"获取报告摘要失败: {e}")
            return None


# 便捷函数
def attach_response(response: Response, name: str = "HTTP响应"):
    """便捷函数：添加HTTP响应附件"""
    AllureHelper.attach_response(response, name)


def attach_request(method: str, url: str, **kwargs):
    """便捷函数：添加HTTP请求附件"""
    AllureHelper.attach_request(method, url, **kwargs)


def step(title: str):
    """便捷函数：创建测试步骤"""
    return AllureHelper.step(title)


# 装饰器
def api_test(epic: str = "API接口测试", feature: str = None, story: str = None, 
            severity: allure.severity_level = allure.severity_level.NORMAL):
    """
    API测试装饰器
    
    Args:
        epic: 史诗
        feature: 功能
        story: 用户故事
        severity: 严重程度
    """
    return AllureHelper.set_test_info(epic=epic, feature=feature, story=story, severity=severity)


if __name__ == "__main__":
    # 示例用法
    print("Allure工具类")
    print("提供丰富的Allure测试报告功能支持")
    
    # 创建报告生成器
    reporter = AllureReporter()
    
    # 生成报告
    if reporter.generate_report():
        print("报告生成成功")
    
    # 获取报告摘要
    summary = reporter.get_report_summary()
    if summary:
        print(f"报告摘要: {summary}")